﻿#region Libraries

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using ESF.DMS.DataAccess;
using ESF.General;
using ESF.General.Entity;
using System.Threading.Tasks;
using System.Threading;
using System.Timers;
using ESF.DMS.Entities;
using ESF.FileProcessing;

#endregion


namespace ESF.DMS.FileMonitor
{
	public partial class ESF_DMS_File_Monitor : ESF.General.BaseWindowsService
	{
		#region Properties

		public bool CheckExistentAtStart
		{
			get
			{
				return ReadConfig.GetAppSettingBool ("CheckExistentAtStart", false);
			}
		}

		#endregion


		#region Members

		private CancellationTokenSource _ProcessingOCRTasks_Cancel;

		private List<FileSystemWatcher> _FSWatchers;

		private bool _IsCheckedExistents;

		private bool _IsRefreshingFSW;

		#endregion


		#region Constructor

		public ESF_DMS_File_Monitor ()
		{
			InitializeComponent ();

			_FSWatchers = new List<FileSystemWatcher> ();
		}

		#endregion


		#region Methods

		private void DeleteFile (FileInfo pFile)
		{
			try
			{
				AdoFileReference.Delete (new FileReference (pFile));
			}
			catch (Exception pEx)
			{
				Log.Error (pEx);
			}
		}


		private void RegisterFile (FileInfo pFile, object [] pParams)
		{
			BasicReference monDir;


			try
			{
				if (pParams == null)
				{
					monDir = AdoFileReference.GetMonitoredDirectories ().Where (
							md => pFile.FullName.StartsWith ((md.Extra_I.EndsWith (Path.DirectorySeparatorChar.ToString ()) ? md.Extra_I : md.Extra_I + Path.DirectorySeparatorChar))).First ();
				}
				else
				{
					monDir = pParams [0] as BasicReference;
				}

				AdoFileReference.Register (new FileReference (pFile) { 
					MonitoredDirectory = monDir, 
					FileType = new BasicReference () { 
						Code = pFile.Extension, 
						BasicReferenceType = new BasicReferenceType () { ID = (int) DMSBasicReferenceTypes.FileTypes } } 
					});
			}
			catch (Exception pEx)
			{
				Log.Error (pEx);
			}
		}


		private FileSystemWatcher GenerateFSW (BasicReference pMonDir)
		{
			string directoryPath;
			string [] extensions;
			string category;

			int count;


			directoryPath = pMonDir.Extra_I;
			category = pMonDir.Code;
			extensions = TextHelper.Split('|', pMonDir.Extra_III).ToArray ();

			FileSystemWatcher fsw;

			Stopwatch sw = new Stopwatch ();


			Log.WriteLineHistory(string.Format("Adding directory [{0}]: {1} with extensions: {2}", category, directoryPath, string.Join(",", extensions)));

			sw.Start ();

			// Registrar todos los archivos
			count = FileHelper.ExecuteInDirectoryFiles (directoryPath, RegisterFile, new object [] { pMonDir }, extensions);

			fsw = new FileSystemWatcher(directoryPath);


			fsw.IncludeSubdirectories = true;

			fsw.Renamed += new RenamedEventHandler(fsw_Renamed);
			fsw.Changed += new FileSystemEventHandler(fsw_Changed);
			fsw.Error += new ErrorEventHandler(fsw_Error);

			fsw.EnableRaisingEvents = true;

			sw.Stop ();

			Log.WriteLineHistory(string.Format ("Readed {0} files with for code {1} and extensions [{2}] in {3}", 
				count, category, string.Join(",", extensions), TextHelper.ToStringMS(sw.ElapsedMilliseconds)));


			return fsw;
		}

		
		private void RefreshFSWatchers ()
		{
			List<BasicReference> monitoredDirectories = null;


			if (_IsRefreshingFSW)
			{
				return;
			}

			_IsRefreshingFSW = true;
			

			Log.WriteLineHistory (string.Format ("Listing monitored directories... (FSW = {0})", _FSWatchers.Count));
			
			monitoredDirectories = AdoFileReference.GetMonitoredDirectories();
			

			if (DataHelper.IsEmpty (monitoredDirectories))
			{
				Log.WriteLineHistory ("There are not monitored directories");
				return;
			}

			
			Log.WriteLineHistory ("Registered directories: {0}", monitoredDirectories.Count);

			Log.WriteLineHistory ("Disabling FSWs...");

			foreach (FileSystemWatcher fsw in _FSWatchers)
			{
				fsw.EnableRaisingEvents = false;
			}


			Parallel.ForEach<BasicReference> (monitoredDirectories,
				ProcessHelper.DefaultParallelOptions,
				delegate (BasicReference pMonDir)
				{
					FileSystemWatcher offFSW;

					offFSW = _FSWatchers.FirstOrDefault(fsw => fsw.Path == FileHelper.AddFinalSeparator(pMonDir.Extra_I));


					if (Directory.Exists(pMonDir.Extra_I))
					{
						Log.WriteLineHistory("Directory found: {0}", pMonDir.Extra_I); 

						if (offFSW == null)
						{ 						
							Log.WriteLineHistory (" - Adding FSW to {0}", pMonDir.Extra_I);
							_FSWatchers.Add(GenerateFSW(pMonDir));
						}
						else
						{
							Log.WriteLineHistory(" - Enabled FSW to {0}", pMonDir.Extra_I);
							offFSW.EnableRaisingEvents = true;
						}
					}
					else
					{
						Log.WriteLineHistory("Directory not found: {0}", pMonDir.Extra_I);
						
						AdoFileReference.Delete(new FileReference() { MonitoredDirectory = pMonDir });
					}
				});

			_FSWatchers.RemoveAll(fsw => !fsw.EnableRaisingEvents);

			Log.WriteLineHistory(string.Format ("Monitoring (FSW) {0} directories.", _FSWatchers.Count));
			

			_IsRefreshingFSW = false;
		}


		private void CheckExistentFileReferences ()
		{
			Stopwatch stopWatch = new Stopwatch();
				
			int count = 0;
			int deletedCount = 0;


			if (_IsCheckedExistents)
			{
				return;
			}

			_IsCheckedExistents = true;

			
			foreach (FileSystemWatcher fsw in _FSWatchers)
			{
				stopWatch.Start();

				Log.WriteHistory("Checking existent for {0}", fsw.Path);

				Parallel.ForEach<FileReference> (AdoFileReference.ListAll (fsw.Path), 
					ProcessHelper.DefaultParallelOptions,
					delegate (FileReference pCurrent)
					{
						Interlocked.Increment (ref count);

						if (!File.Exists (pCurrent.URL))
						{
							Interlocked.Increment(ref deletedCount);

							Log.WriteLineHistory ("Deleted reference to unexistent file: {0}", pCurrent.URL);

							AdoFileReference.Delete (pCurrent);
						}
						// Verifica las imagenes extraidas
						else
						{
							ProcessFileHelper.ExtractImages (pCurrent.URL);
						}
					});

				stopWatch.Stop();

				Log.WriteLineHistory(" in {0} for {1} files, deleted not existent {2}", 
					TextHelper.ToStringMS(stopWatch.ElapsedMilliseconds), count, deletedCount);

				count = 0;
				deletedCount = 0;

				stopWatch.Reset ();
			}

			AdoFileReference.UpdateParents ();

			Log.WriteLineHistory ("Updated parents for files");

			_IsCheckedExistents = false;
		}

		#endregion


		#region Eventos FSW

		private void fsw_Error(object sender, ErrorEventArgs e)
		{
			Log.Error (e.GetException ());
		}


		private void fsw_Changed(object sender, FileSystemEventArgs e)
		{
			Task.Factory.StartNew (delegate () 
			{
				switch (e.ChangeType)
				{
					case WatcherChangeTypes.Changed:
					case WatcherChangeTypes.Created:			
						DeleteFile (new FileInfo (e.FullPath));
						RegisterFile (new FileInfo (e.FullPath), null);
					break;

					case WatcherChangeTypes.Deleted:
						DeleteFile (new FileInfo (e.FullPath));
					break;
				}
			});
		}


		private void fsw_Renamed(object sender, RenamedEventArgs e)
		{
			Task.Factory.StartNew (delegate () 
			{
				DeleteFile(new FileInfo(e.OldFullPath));
				RegisterFile(new FileInfo(e.FullPath), null);
			});
		}

		#endregion


		#region Service Methods

		protected override void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
		{
			Stopwatch sw = new Stopwatch();
			

			try
			{
				Log.WriteLineHistory(string.Format("Timer runned at: {0}", DateTime.Now));

				RefreshFSWatchers();

				CheckExistentFileReferences();

				// Ejecuta si esta entre las 11pm del dia actual y antes de las 7am del dia actual
				if (
					DateTime.Compare(DateTime.Now, new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 9, 0, 0)) > 0
					|| DateTime.Compare(DateTime.Now, new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 7, 0, 0)) < 0)
				{
					Log.WriteLineHistory("Start running tasks for non working hours (9am hoy 7am manana)...");


					#region Process OCR

					// No esta andando
					if (_ProcessingOCRTasks_Cancel == null)
					{
						_ProcessingOCRTasks_Cancel = new CancellationTokenSource();


						if (ReadConfig.GetAppSettingBool("IsOCREnabled", false))
						{
							sw.Start();

							Log.WriteLineHistory("Starting processing of OCR at {0} for this Monitored Directory IDs: {1}", TextHelper.ToString(DateTime.Now), ReadConfig.GetAppSetting("OCR_MonitoredDirectoryIDs", string.Empty));

						
							AdoFileReference.ExecTasksForProcessOCRForAllPending(_ProcessingOCRTasks_Cancel,
								TextHelper.ConvertoToIntList(
									TextHelper.Split(",", ReadConfig.GetAppSetting("OCR_MonitoredDirectoryIDs", string.Empty))).ToArray());

							sw.Stop();

							Log.WriteLineHistory("OCR processing completed at {0} in {1}", TextHelper.ToString(DateTime.Now), TextHelper.ToStringMS(sw.ElapsedMilliseconds));
						}
					}

					#endregion
				}
				else
				{
					// Si aun esta andando debe detenerse
					if (_ProcessingOCRTasks_Cancel != null)
					{
						Log.WriteLineHistory(string.Format("Cancel OCR process at: {0}", DateTime.Now));

						_ProcessingOCRTasks_Cancel.Cancel();
					}
				}
			}
			catch (Exception pEx)
			{
				Log.Error(pEx);
			}
		}


		public override void DoStop()
		{
			Parallel.ForEach<FileSystemWatcher> (_FSWatchers, ProcessHelper.DefaultParallelOptions, delegate (FileSystemWatcher pFsw)
			{
				pFsw.EnableRaisingEvents = false;
				pFsw.Dispose ();
			});

			_FSWatchers.Clear ();
		}

		#endregion
	}
}
